#ifndef VEHICLE_HPP_
#	define VEHICLE_HPP_

#   include "VRPTW.hpp"
#   include <map>
#   include <deque>
#	include <vector>

#   define  MIN(A,B)    ((A <= B) ? A : B)

class   Solution;

struct VehicleEvalResult
{
	bool        erNewLastCritical;
    bool	    erValable;
	double	    erTotalTime;
	double		erTotalDistance;
	double		erTotalWait;
	double		erAdvantage;
    unsigned    erInvalidClient;
    unsigned    erNClient;
    
    std::string     toString();
    std::string		toString2();
};

struct ServiceInfo
{
	ServiceInfo(unsigned inClient)
		: ciClient(inClient)
	{}
	
	unsigned	ciClient;			// numero du client
	
	
	
	double		ciArrivalDate;		// date d'arrivee chez le client
	double		ciDepartureDate;	// date ou le vehicule est pret a partir
	double		ciWaitTime;			// temps d'attente d'ouverture du client
	double		ciStayTime;			// temps total passé chez le client
	
	unsigned	ciAdmissibleRetardTime;	// temps correspondant au retard admissible
										// par rapport au clients suivants (par exemple D -> 1 -> 3 -> 5 -> D)
										// avec 	ciAdmissibleRetardTime(5) = 0
										// alors 	ciAdmissibleRetardTime(3) = ciAdmissibleRetardTime(5) + ciWaitTime(3)
										// et		ciAdmissibleRetardTime(1) = ciAdmissibleRetardTime(3) + ciWaitTime(1)
										// On peut inserer un client x avant 1 si ciAdmissibleRetardTime(1) >= TEMPS(D -> x -> 1)
		
		// si ciWaitTime(k) == 0
		//	alors ciAdmissibleRetardTime(k) = (ciArrivalDate(k) - clDueDate)
		// sinon
		//  alors ciAdmissibleRetardTime(k) = ciAdmissibleRetardTime(k - 1) + ciWaitTime(k)
		// ciAdmissibleRetardTime(k) = ((ciArrivalDate(k) - clDueDate) < C) ? 0 : ciAdmissibleRetardTime(k - 1) + ciWaitTime(k);
		// avec C = 0.02 par exemple
		
	int			ciCCapacityAtArrival;		// capacite cumulee a l'arrivee
	int			ciCCapacityAtDeparture;		// capacite cumulee au depart
	
	double		ciCDistanceAtArrival;	// distance cumulee a l'arrivee
	double		ciCDistanceAtDeparture;	// distance cumulee au depart (pour depot uniquement)	
	
	
	double		ciCTimeAtArrival;	// temps cumule a la date d'arrivee chez le client
	double		ciCTimeAtReady;		// temps cumule a la date où le vehicule est pret a partir
};



// scanner la tournee depuis la fin

struct Operation
{
    typedef enum { INSERT = 0, ERASE, REPLACE, UNKNOWN } Type;
    
    Operation()
    {
        opType = Operation::UNKNOWN;
        opClient = 0xffffffff;
        opClientReplaced = 0xffffffff;
    }
    
    Operation(Operation::Type   inType, unsigned inClient, unsigned inClientReplaced = 0xffffffff)
        : opType(inType), opClient(inClient), opClientReplaced(inClientReplaced)
    {}
    
    Type        opType;
    unsigned    opClient;
    unsigned    opClientReplaced;
};

// VRPTW NOT UPDATED WHILE INSERTING CLIENTS

class Vehicle
{
	public:
		typedef enum { INSERT_BEGIN = 0, INSERT_END, INSERT_BEFORE, INSERT_BETWEEN }	InsertType;
		typedef enum { OPT_2, OPT_3, OPT_4 } MoveType;
        
        //  R_ANY : on tire dans Clients
        //  R_LC_ACCEPT : on tire dans Clients - Criticals + Last_Critical
        //  R_LC_DENIED : on tire dans Clients - Criticals
		typedef enum { RAND_ANY, RAND_LC_ALLOW, RAND_LC_DENY } RandomGetType;
        
		Vehicle(VRPTW& inVRPTW, unsigned inId);
        Vehicle(const Vehicle& inVehicle);
        Vehicle&    operator=(const Vehicle&    inVehicle);
		~Vehicle();
        
        friend void basic2Opt(Vehicle& in1, Vehicle& in2);
        friend void basicRandom2Opt(Vehicle& in1, Vehicle& in2);
        friend void random2Opt(Vehicle& in1, Vehicle& in2);
        friend void basicRandomOrOpt(Vehicle& in1, Vehicle& in2);
        friend void randomOrOpt(Vehicle& in1, Vehicle& in2);
        friend void localVND(Vehicle& in1, Vehicle& in2, unsigned inMaxIt) EXC_THROWER();
        friend void localVND2(Vehicle& in1, Vehicle& in2, unsigned inMaxIt) EXC_THROWER();
        friend void localVND3(Vehicle& in1, Vehicle& in2);
        friend void testMoves(Vehicle& in1, Vehicle& in2, std::vector<unsigned>& inRes, std::vector<unsigned>& inRes2);
        friend void basicRandomSwap(Vehicle& inVehicle);
        friend void	localAppend(Vehicle& in1, Vehicle& in2);
        friend void basicVND(Vehicle& in1, Vehicle& in2);
        friend bool intersect(Vehicle& in1, Vehicle& in2);
        
		void		clear();
		void		add(unsigned inClient);
		
		unsigned	size() const;
		bool		exist(unsigned inClient) const;
		void		erase(unsigned inClient) EXC_THROWER();
		bool		eval(VehicleEvalResult* inEvalResult) EXC_THROWER();
        
		void		move();
		void        shift();
		void		swap(unsigned inClient1, unsigned inClient2) EXC_THROWER();
		void		insertClient(	InsertType 	inType,
									unsigned 	inClient,
									unsigned 	inA = 0xbaddeb1e,
									unsigned 	inB = 0xbaddeb1e);
                                    
        void        append(const Vehicle& inVehicle) EXC_THROWER();
        void		fillDeque(std::deque<unsigned>& inDeque) const;
		
        void        randomSwap();
        
        unsigned    getRandomClient(RandomGetType   R_Type = Vehicle::RAND_LC_DENY) const;
        
        std::string     toString();
        
        unsigned	getPredecessorOf(unsigned inClient) const EXC_THROWER();
        unsigned    getSuccessorOf(unsigned inClient) const EXC_THROWER();
        
        
        void		moveRandomIntra2Opt();
        void		moveRandomIntra3Opt();
        
        const std::list<Operation>&     getHistory() const
        {   return toHistory;     }
        
        void                            clearHistory()
        {   toHistory.clear();    }
        
        void                            clearAndAddReplace()
        {
            toHistory.clear();
            for (std::map<unsigned, unsigned>::iterator it = toIndexesByClient.begin() ; it != toIndexesByClient.end() ; ++it)
            {
                if (it->first == DEPOT)
                    continue;
                    
                toHistory.push_back(Operation(Operation::REPLACE, 0xdeb1e, it->first));
            }
        }
        
        unsigned		getNbClients() const
        {
			return (toIndexesByClient.size() - 1);
		}
        
        unsigned        getNbClientsNonCriticals() const
        {
            return (this->getNbClients() - this->getNbCriticals());
        }
        
        unsigned        getNbClientsNonCriticalsEx() const
        {
            return (this->getNbClients() - this->getNbCriticals() + 1);
        }
        
        void			addRandomRDepot()
        {
			unsigned	c = this->getRandomClient();
			
			if (c != DEPOT)
			{
				this->insertClient(Vehicle::INSERT_BEFORE, toVRPTW->buildNewRDepot(), c);
			}
		}
		
		void			addRDepotAtEnd()
		{
			this->insertClient(Vehicle::INSERT_END, toVRPTW->buildNewRDepot());
		}
        
        bool			hasRD() const
        {
			std::map<unsigned, unsigned>::const_reverse_iterator	ir;
			
			if (toIndexesByClient.size() == 1)
				return (false);
			
			ir = toIndexesByClient.rbegin();
			ir++;
			return (toVRPTW->isRDepot(ir->first));
		}
		
		unsigned		getNbRD() const
		{
			unsigned	n = 0;
			std::map<unsigned, unsigned>::const_reverse_iterator	ir;
			
			if (toIndexesByClient.size() == 1)
				return (0);
			
			ir = toIndexesByClient.rbegin();
			
			do
			{
				ir++;
				n++;
			}	while (toVRPTW->isRDepot(ir->first));
			n--;
			
			return (n);
		}
        
        unsigned    getNbCriticals() const
        {
            return (toNbCriticals);
        }
        
        //void		evalFrom(VehicleEvalResult* inEval);
        
        //
        unsigned  toId; 
              
	protected:
    	VRPTW*					        toVRPTW;
    	
    	unsigned						toEvalStart;
    	
		std::map<unsigned, unsigned>	toIndexesByClient;
		std::deque<unsigned>			toClients;      // a l'erase d'un client, cette struct n'est pas videe, comme toutes les autres
                                                        // utilisation d'un attr toSize supplementaire (parce que sinon mise a jour de 
                                                        // toute la map, et il vaut mieux consommer de la memoire que du CPU etant donne
                                                        // le nombre d'erase qui vont avoir lieu)
                                                        // seule la map (toIndexesByClient) est updated (erase unique)
		std::deque<unsigned>			toPreds;
		std::deque<unsigned>			toSuccs;
		//std::deque<double>				toDistances;	// distances cumulees
		//std::deque<int>					toCapacities;	// capacites cumulees
		//std::deque<double>				toEndDates;		// dates de fin de chaque client
        
        std::deque<VehicleEvalResult>   toEvals;
        
        std::map<unsigned, unsigned>    toFreeSlots;
        
        std::list<Operation>            toHistory;
        
        unsigned                        toNbCriticals;
        unsigned                        toLastCritical;
        unsigned                        toSize;
        
        unsigned						toNRDepot;
        
		void			insert_at_beginning(unsigned inClient);
		void			insert_at_end(unsigned inClient);
		void			insert_between(unsigned inClient, unsigned inPred, unsigned inSucc);
        void            insert_with_min_distance(unsigned inClient);
        
        unsigned        get_rand_any() const;
        unsigned        get_rand_lc_allow() const;
        unsigned        get_rand_lc_deny() const;
};

#endif
